import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader'
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader'
import { CSS2DRenderer } from 'three/examples/jsm/renderers/CSS2DRenderer'
import { CSS3DRenderer } from 'three/examples/jsm/renderers/CSS3DRenderer'
import { DragControls } from 'three/examples/jsm/controls/DragControls.js'

class RenderModel {
  constructor(selector, bgColor) {
    this.container = document.querySelector(selector)
    // 场景
    this.scene = null
    // 相机
    this.camera = null
    // 渲染器
    this.renderer = null
    this.renderer2D = null
    this.renderer3D = null
    // 灯光
    this.light = null
    // 控制器
    this.controls = null
    this.dragControls = null
    // 加载模型列表
    this.modelList = []
    // 动画
    this.mixer = null
    this.clipAction = null
    this.clock = new THREE.Clock()
    // 拖拽器
    this.dragObj = []
    this.dragControl = null
    // 背景颜色 '#C5CEE0'
    this.bgColor = bgColor || ''
    this.fileLoaderMap = {
      glb: new GLTFLoader(),
      fbx: new FBXLoader(),
      gltf: new GLTFLoader(),
      obj: new OBJLoader(),
      png: new THREE.TextureLoader(),
    }
    // 导入模型缩放程度
    this.scale = 1
  }
  // 初始化
  init() {
    this.initScene()
    this.initCamera()
    this.initRender()
    window.addEventListener('resize', this.onWindowResize.bind(this))
  }
  initScene() {
    this.scene = new THREE.Scene()
    this.scene.background = this.bgColor ? new THREE.Color(this.bgColor) : null
    // 环境光
    this.light = new THREE.AmbientLight(0xffffff, 2)
    const pointLight = new THREE.PointLight(0xffffff, 1, 100, 0)
    pointLight.position.set(0, 10, 10)
    // const pointLightHelper = new THREE.PointLightHelper(pointLight, 5);
    this.scene.add(this.light)
    this.scene.add(pointLight)
    // this.scene.add(pointLightHelper);
  }
  initCamera() {
    const { clientHeight, clientWidth } = this.container
    this.camera = new THREE.PerspectiveCamera(
      30,
      clientWidth / clientHeight,
      0.1,
      1000
    )
    this.setCameraPositon(new THREE.Vector3(0, 32, 0))
  }
  initRender() {
    this.renderer = new THREE.WebGLRenderer({
      antialias: true,
      alpha: true,
      preserveDrawingBuffer: true,
    }) // 设置抗锯齿
    // 设置屏幕像素比
    this.renderer.setPixelRatio(window.devicePixelRatio)
    // 渲染尺寸大小
    const { clientHeight, clientWidth } = this.container
    this.renderer.setSize(clientWidth, clientHeight)
    this.container.appendChild(this.renderer.domElement)
    // 创建一个CSS2渲染器CSS2DRenderer
    this.renderer2D = new CSS2DRenderer()
    this.renderer2D.setSize(clientWidth, clientHeight)
    this.renderer2D.domElement.style.position = 'absolute'
    // 相对标签原位置位置偏移大小
    this.renderer2D.domElement.style.top = '0px'
    this.renderer2D.domElement.style.left = '0px'
    // 设置.pointerEvents=none，以免模型标签HTML元素遮挡鼠标选择场景模型
    this.renderer2D.domElement.style.pointerEvents = 'none'
    this.container.appendChild(this.renderer2D.domElement)

    // 创建一个CSS3D渲染器CSS3DRenderer
    this.renderer3D = new CSS3DRenderer()
    this.renderer3D.setSize(clientWidth, clientHeight)
    this.renderer3D.domElement.style.position = 'absolute'
    // 相对标签原位置位置偏移大小
    this.renderer3D.domElement.style.top = '0px'
    this.renderer3D.domElement.style.left = '0px'
    // 设置.pointerEvents=none，以免模型标签HTML元素遮挡鼠标选择场景模型
    this.renderer3D.domElement.style.pointerEvents = 'none'
    this.container.appendChild(this.renderer3D.domElement)

    // 设置相机控件
    this.initControls()
    // 渲染场景
    this.render()
  }
  initControls() {
    this.controls = new OrbitControls(this.camera, this.renderer.domElement)
    this.controls.minDistance = 2
    this.controls.maxDistance = 60
  }
  resetControl() {
    this.controls && this.controls.reset()
  }
  render() {
    const animate = () => {
      // 循环调用函数
      this.clearAnim = requestAnimationFrame(animate)
      // 更新相机控件
      this.controls.update()
      this.mixer && this.mixer.update(this.clock.getDelta())
      // 渲染界面
      this.renderer.render(this.scene, this.camera)
      this.renderer2D.render(this.scene, this.camera)
      this.renderer3D.render(this.scene, this.camera)
    }
    animate()
  }
  onWindowResize() {
    const { clientHeight, clientWidth } = this.container
    this.renderer.setSize(clientWidth, clientHeight)
    this.renderer2D.setSize(clientWidth, clientHeight)
    this.renderer3D.setSize(clientWidth, clientHeight)
    this.camera.aspect = clientWidth / clientHeight
    this.camera.updateProjectionMatrix()
  }
  // 加载模型
  setModel({ filePath, fileType, position }) {
    return new Promise((resolve, reject) => {
      const loader = this.fileLoaderMap[fileType]
      let currentModel = null
      loader.load(filePath, (result) => {
        switch (fileType) {
          case 'glb':
          case 'gltf':
            currentModel = result.scene
            break
          case 'fbx':
          case 'obj':
            currentModel = result
            break
          case 'png':
            currentModel = result
            break
          default:
            break
        }
        if (fileType === 'png') {
          const SIZE = 20
          const img = currentModel.image
          let height = (img && img.height) || SIZE
          let width = (img && img.width) || SIZE
          height = (SIZE / width) * height
          width = SIZE
          const mat = new THREE.MeshBasicMaterial({
            map: currentModel,
            side: THREE.DoubleSide,
            transparent: true,
          })
          const geom = new THREE.PlaneGeometry(width, height)
          const mesh = new THREE.Mesh(geom, mat)
          this.scene.add(mesh)
          resolve(currentModel)
        } else {
          if (currentModel) {
            const box = new THREE.Box3().setFromObject(currentModel)
            const size = box.getSize(new THREE.Vector3())
            const maxSize = Math.max(size.x, size.y, size.z)
            this.scale = maxSize < 1 ? 0.5 : 14 / maxSize
            currentModel.scale.set(this.scale, this.scale, this.scale)
            if (position) {
              const { x, y, z } = position
              currentModel.position.set(x, y, z)
            }
            // this.changeOpacity(currentModel)
            this.modelList.push(currentModel)
            this.scene.add(currentModel)
            resolve(currentModel)
          }
        }
      })
    })
  }
  changeOpacity(obj) {
    if (obj.material) {
      obj.material.transparent = true
      obj.material.opacity = 0.9
    } else {
      if (obj.children) {
        obj.children.forEach((item) => {
          this.changeOpacity(item)
        })
      }
    }
  }
  // 设置相机位置
  setCameraPositon(
    position = { x: 0, y: 0, z: 0 },
    lookPosition = { x: 0, y: 0, z: 0 }
  ) {
    if (!this.camera) return
    const { x: x1, y: y1, z: z1 } = position
    const { x: x2, y: y2, z: z2 } = lookPosition
    this.camera.position.set(x1, y1, z1)
    this.camera.lookAt(x2, y2, z2)
  }
  clearAllModel() {
    this.modelList.forEach((item) => {
      this.scene.remove(item)
    })
  }
  initAnimation() {
    const scaleKeyFrame = new THREE.VectorKeyframeTrack(
      '.scale',
      [0, 1],
      [0.3, 0.3, 0.3, 1, 1, 1]
    )
    const clip = new THREE.AnimationClip('changeView', 1, [scaleKeyFrame])
    this.mixer = new THREE.AnimationMixer(this.scene)
    this.clipAction = this.mixer.clipAction(clip)
    this.clipAction.play()
    this.mixer.addEventListener('loop', () => {
      this.clipAction.stop()
    })
  }
  animationUpdate() {
    this.clipAction.reset()
    this.clipAction.play()
  }
  // 拖拽器
  initDragControl(arr) {
    this.dragControl = new DragControls(
      arr,
      this.camera,
      this.renderer.domElement
    )
  }
  clearDragControl() {
    this.dragControl && this.dragControl.dispose()
  }
}

export default RenderModel
